using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Framework.Core;
using UnityEngine;
using UnityEngine.Audio;

namespace Framework
{
    public class AudioManager : EventDispatcher
    {
        internal class Sign
        {

        }
        public bool Enabled { get; private set; }
        /// <summary>
        /// 声音停止后卸载等待时间
        /// </summary>
        public float UnloadWaitTime = 8f;
        private int _identifyingCode;
        public AudioMixer AudioMixer { get; private set; }
        private AssetLoader _loader;
        private Transform _parent;
        private Sign _sign;
        private List<AudioMixerGroup> _groupList;
        private Dictionary<string, AudioClipLoad> _clipMap;
        private CustomPool<AudioParams> _paramPool;
        private CustomPool<AudioAgent> _agentPool;
        private Dictionary<int, AudioAgent> _id2AgentMap;
        private List<AudioAgent> _workingAgentList;
        private float _time;
        private float[] _delayConfigTime;
        private float[] _delayTime;
        private LinkedList<AudioClipLoad> _unloadList;
        public AudioManager(AssetLoader loader)
        {
            UnloadWaitTime = 8f;
            _workingAgentList = new List<AudioAgent>();
            _identifyingCode = UnityEngine.Random.Range(0, 1000000);
            _clipMap = new Dictionary<string, AudioClipLoad>(StringComparer.Ordinal);
            _id2AgentMap = new Dictionary<int, AudioAgent>();
            _loader = loader;
            _sign = new Sign();
            _paramPool = new CustomPool<AudioParams>(() => { return new AudioParams(_sign); });
            _agentPool = new CustomPool<AudioAgent>(() =>
            {
                var go = new GameObject();
                go.transform.SetParent(_parent, false);
                var agent = go.AddComponent<AudioAgent>();
                return agent;
            });
            _unloadList = new LinkedList<AudioClipLoad>();
        }
        public void Initialize(AudioMixer mixer, Transform root, string[] groups)
        {
            if (AudioMixer != null) return;
            if (groups.Length < 1)
            {
                FrameworkLog.LogError("没有指定混合组");
                return;
            }
            this.AudioMixer = mixer;
            _parent = root;
            var behaviour = root.gameObject.AddComponent<AudioManagerBehaviour>();
            behaviour.updateHandler = update;
            _groupList = new List<AudioMixerGroup>(groups.Length);
            _delayConfigTime = new float[groups.Length];
            _delayTime = new float[groups.Length];
            var len = groups.Length;
            for (int i = 0; i < len; i++)
            {
                _delayConfigTime[i] = 0;
                _delayTime[i] = 0;
                _groupList.Add(null);
                var list = mixer.FindMatchingGroups(groups[i]);
                if (list.Length > 0)
                {
                    _groupList[i] = list[0];
                }
                else
                {
                    FrameworkLog.LogErrorFormat("混合组{0}没有找到", groups[i]);
                }
            }
            Enabled = true;
        }
        public void SetIntervalTime(int group, float interval)
        {
            if (!Enabled) return;
            if (group < 0 || group >= _delayConfigTime.Length) return;
            _delayConfigTime[group] = interval;
        }
        public void SetFloat(string name, float value)
        {
            if (!Enabled) return;
            AudioMixer.SetFloat(name, value);
        }
        public AudioParams Play(string path, int group = 0)
        {
            if (group < 0 || group >= _groupList.Count)
            {
                FrameworkLog.LogErrorFormat("{0}混合组出错", path);
                return null;
            }
            var delayTime = _delayTime[group];
            if (delayTime > 0) return null;
            _delayTime[group] = _delayConfigTime[group];
            var mixerGroup = _groupList[group];
            if (!_clipMap.TryGetValue(path, out AudioClipLoad load))
            {
                load = new AudioClipLoad();
                load.AssetPath = path;
                _clipMap.Add(path, load);
                _loader.LoadAsset(path, load.Success);
            }
            var param = _paramPool.Get();
            param.Initialize(_identifyingCode++, _sign);
            var id = param.SoundId;
            var agent = _agentPool.Get();
            agent.Set(param, path, mixerGroup);
            agent.SetStatus(MediaStatus.Play);
            _id2AgentMap.Add(id, agent);
            _workingAgentList.Add(agent);
            return param;
        }
        public void Play(int sound)
        {
            var agent = GetAudioAgent(sound);
            if (agent == null) return;
            agent.SetStatus(MediaStatus.Play);
        }
        public void Stop(int sound)
        {
            var agent = GetAudioAgent(sound);
            if (agent == null) return;
            agent.SetStatus(MediaStatus.Stop);
        }
        public void Pause(int sound)
        {
            var agent = GetAudioAgent(sound);
            if (agent == null) return;
            agent.SetStatus(MediaStatus.Pause);
        }
        public void SetTime(int sound, float normalizeTime)
        {
            var agent = GetAudioAgent(sound);
            if (agent == null) return;
            agent.SetTime(normalizeTime);
        }
        private AudioAgent GetAudioAgent(int soundId)
        {
            if (_id2AgentMap.TryGetValue(soundId, out AudioAgent agent))
            {
                return agent;
            }
            return null;
        }
        private void update(float deltaTime)
        {
            _time += deltaTime;
            var len = _delayTime.Length;
            while (len-- > 0)
            {
                if (_delayTime[len] > 0)
                {
                    _delayTime[len] -= deltaTime;
                }
            }
            len=_workingAgentList.Count;
            while(len-- > 0)
            {
                var agent = _workingAgentList[len];
                agent.UpdateState(deltaTime);
                if (agent.Finished)
                {
                    if (_clipMap.TryGetValue(agent.AssetPath, out AudioClipLoad load))
                    {
                        if (load.Finished)
                        {
                            load.PushAudioClip(agent.AudioClip);
                            if (load.RefCount < 1)
                            {
                                if (!_unloadList.Contains(load))
                                {
                                    _unloadList.AddLast(load);
                                }
                            }
                        }
                    }
                    var id = agent.Params.SoundId;
                    _id2AgentMap.Remove(id);
                    _paramPool.Release(agent.Params);
                    agent.Clear();
                    _agentPool.Release(agent);
                    _workingAgentList.RemoveAt(len);
                }
                else if (agent.AudioClip == null)
                {
                    if (_clipMap.TryGetValue(agent.AssetPath, out AudioClipLoad load))
                    {
                        if (load.Finished)
                        {
                            agent.SetAudioClip(load.PopAudioClip());
                        }
                    }
                }
            }
            var current = _unloadList.First;
            while (current != null)
            {
                var node = current;
                var item = node.Value;
                var next = node.Next;
                item.Update(deltaTime);
                if (item.RefCount > 0)
                {
                    _unloadList.Remove(node);
                }
                else if (item.WaitUnloadTime > UnloadWaitTime)
                {
                    _unloadList.Remove(node);
                    _clipMap.Remove(item.AssetPath);
                    _loader.Unload(item.UnloadToken);
                    item.Clear();

                }
                current = next;
            }
        }
        internal class AudioManagerBehaviour : MonoBehaviour
        {
            public Action<float> updateHandler;
            private void Update()
            {
                if (updateHandler != null)
                {
                    updateHandler.Invoke(Time.deltaTime);
                }
            }
        }
        internal class AudioClipLoad
        {
            public string AssetPath;
            public IAssetResult UnloadToken;
            public float WaitUnloadTime { get; private set; }
            public bool Finished { get; private set; }
            public int RefCount { get; private set; }
            private AudioClip _clip;
            public void Success(IAssetResult result)
            {
                this.Finished = true;
                if (result.Asset is AudioClip clip)
                {
                    _clip = clip;
                }
                UnloadToken = result;
            }
            public AudioClip PopAudioClip()
            {
                RefCount++;
                return _clip;
            }
            public void PushAudioClip(AudioClip value)
            {
                if (value != _clip) return;
                RefCount--;
                if (RefCount < 1)
                {
                    RefCount = 0;
                    WaitUnloadTime = 0;
                }
            }
            public void Update(float delayTime)
            {
                WaitUnloadTime += delayTime;
            }
            public void Clear()
            {
                _clip = null;
            }
        }
        internal class AudioAgent : MonoBehaviour, IAudioAgent
        {
            private static Dictionary<string, Action<AudioAgent, float>> _setFloatValueAction;
            [NonSerialized]
            public AudioSource audioSource;
            public string AssetPath { get; private set; }
            public AudioClip AudioClip { get; private set; }
            public bool Finished { get; private set; }
            public AudioParams Params { get; private set; }
            private AudioMixerGroup mixerGroup;
            private float _startTime;
            private float _totalTime;
            private MediaStatus _nowStatus;
            private MediaStatus _action;

            private void Awake()
            {
                audioSource = this.gameObject.AddComponent<AudioSource>();
                if (_setFloatValueAction == null)
                {
                    _setFloatValueAction = new Dictionary<string, Action<AudioAgent, float>>();
                    _setFloatValueAction.Add("mute", (agent, value) =>
                    {
                        agent.audioSource.mute = value > 0f;
                    });
                    _setFloatValueAction.Add("priority", (agent, value) =>
                    {
                        agent.audioSource.priority = Mathf.FloorToInt(value);
                    });
                    _setFloatValueAction.Add("volume", (agent, value) =>
                    {
                        agent.audioSource.volume = value;
                    });
                    _setFloatValueAction.Add("panStereo", (agent, value) =>
                    {
                        agent.audioSource.panStereo = value;
                    });
                    _setFloatValueAction.Add("spatialBlend", (agent, value) =>
                    {
                        agent.audioSource.spatialBlend = value;
                    });
                    _setFloatValueAction.Add("minDistance", (agent, value) =>
                    {
                        agent.audioSource.minDistance = value;
                    });
                    _setFloatValueAction.Add("maxDistance", (agent, value) =>
                    {
                        agent.audioSource.maxDistance = value;
                    });
                    _setFloatValueAction.Add("spread", (agent, value) =>
                    {
                        agent.audioSource.spread = value;
                    });
                    _setFloatValueAction.Add("rolloffMode", (agent, value) =>
                    {
                        AudioRolloffMode mode = AudioRolloffMode.Logarithmic;
                        int ivalue = Mathf.FloorToInt(value);
                        if (ivalue == 1)
                        {
                            mode = AudioRolloffMode.Linear;
                        }
                        agent.audioSource.rolloffMode = mode;
                    });
                }
            }
            public void Set(AudioParams param, string path, AudioMixerGroup group)
            {
                _startTime = 0;
                this.Params = param;
                this.AssetPath = path;
                this.mixerGroup = group;
                Finished = false;
                _nowStatus = MediaStatus.None;
                _action = MediaStatus.None;
                this.gameObject.name = string.Format("{0}[{1}]", mixerGroup.name, Path.GetFileNameWithoutExtension(AssetPath));
                AudioClip = null;
                audioSource.Stop();
            }
            public void SetAudioClip(AudioClip value)
            {
                this.AudioClip = value;
                if (value == null)
                {
                    this.Finished = true;
                }
                else
                {
                    audioSource.clip = value;
                    audioSource.loop = Params.Loop;
                    audioSource.playOnAwake = false;
                    audioSource.outputAudioMixerGroup = mixerGroup;
                    _totalTime = value.length;
                    this.gameObject.name = string.Format("[{0}]{2} {1}", mixerGroup.name, Path.GetFileNameWithoutExtension(AssetPath), Params.Loop ? " ∞" : "");
                }
            }
            public void Clear()
            {
                this.Params = null;
                this.Finished = true;
                this.gameObject.name = "Audio[Idle]";
                this.audioSource.clip = null;
                AudioClip = null;
                audioSource.Stop();
                _startTime = 0f;
            }
            public void SetStatus(MediaStatus value)
            {
                _action = value;
            }
            public void SetTime(float value)
            {
                if (Finished) return;
                if (this.Params == null) return;
                if (_nowStatus == MediaStatus.Pause || _nowStatus == MediaStatus.Play)
                {
                    audioSource.time = Mathf.Clamp01(value) * AudioClip.length;
                }
            }
            public void SetFloat(string name, float value)
            {
                if (_setFloatValueAction.TryGetValue(name, out Action<AudioAgent, float> action))
                {
                    action.Invoke(this, value);
                }
            }
            public void UpdateState(float deltaTime)
            {
                _startTime += deltaTime;
                if (Finished) return;
                if (this.Params == null) return;
                if (this.Params.ParamsChanged)
                {
                    this.Params.ReadParams(this);
                }
                if (Params.FollowTarget != null)
                {
                    this.transform.position = Params.FollowTarget.position;
                }
                if (this.AudioClip == null) return;
                if (audioSource != null)
                {
                    if (audioSource.time >= _totalTime || (audioSource.time < 0.01f && _startTime >= _totalTime))
                    {
                        if (!this.Params.Loop)
                        {
                            this.Finished = true;
                            return;
                        }
                    }
                }
                if (_action != MediaStatus.None)
                {
                    var status = _action;
                    _action = MediaStatus.None;
                    if (status != _nowStatus)
                    {
                        var nowStatus = _nowStatus;
                        _nowStatus = _action;
                        if (status == MediaStatus.Pause)
                        {
                            if (nowStatus == MediaStatus.Play)
                            {
                                audioSource.Pause();
                            }
                        }
                        else if (status == MediaStatus.Play)
                        {
                            if (nowStatus == MediaStatus.None)
                            {
                                audioSource.Play();
                                if (_startTime >= AudioClip.length)
                                {
                                    this.Finished = true;
                                    return;
                                }
                                _startTime = 0;
                                if (Params.NormalizeTime >= 0)
                                {
                                    audioSource.time = Mathf.Clamp01(Params.NormalizeTime) * AudioClip.length + _startTime;
                                }
                            }
                            else if (nowStatus == MediaStatus.Pause)
                            {
                                audioSource.UnPause();
                            }
                        }
                        else if (status == MediaStatus.Stop)
                        {
                            audioSource.Stop();
                            this.Finished = true;
                        }
                    }

                }
            }

        }
    }
}